﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Collections.Specialized;
using System.Linq.Dynamic;
using System.Globalization;

namespace Catalyst.Web.DynamicData
{
    /// <summary>
    /// A CompositeExpressionParameter is a parameter that contains a collection of parameters which are evaluated together.  An example
    /// of this is either a boolean AND or OR operation.  CompositeExpressionParameters store these sub parameters in the Parameters property.
    /// If the parameter within the Parameters property implements IDynamicExpressionParameter the LambdaExpression returned by GetLambdaExpression()
    /// will be used during evalution; otherwise a Lambda Expression will be dynamically created to preform an equality check.
    /// </summary>
    public abstract class CompositeExpressionParameterBase : DynamicExpressionParameterBase, IStateManager
    {
        private DynamicParameterCollection _parameters;
        private List<Pair> _values;

        /// <summary>
        /// ContainedParameters are stored in this property.  
        /// </summary>
        [Browsable(false), DefaultValue((string)null), Category("Data"), PersistenceMode(PersistenceMode.InnerProperty), MergableProperty(false)]
        public DynamicParameterCollection Parameters
        {
            get
            {
                if (this._parameters == null)
                {
                    this._parameters = new DynamicParameterCollection();
                }
                return this._parameters;
            }
        }

        #region IStateManager

        bool IStateManager.IsTrackingViewState
        {
            get
            {
                return base.IsTrackingViewState;
            }
        }

        void IStateManager.LoadViewState(object state)
        {
            if (state == null)
            {
                base.LoadViewState(null);
            }
            else
            {
                Pair pair = (Pair)state;
                base.LoadViewState(pair.First);
                if (pair.Second != null)
                {
                    ((IStateManager)Parameters).LoadViewState(pair.Second);
                }
            }
        }

        object IStateManager.SaveViewState()
        {
            Pair pair = new Pair();
            pair.First = base.SaveViewState();
            if (Parameters.Count > 0)
            {
                pair.Second = ((IStateManager)Parameters).SaveViewState();
            }
            if ((pair.First == null) && (pair.Second == null))
            {
                return null;
            }
            return pair;
        }

        void IStateManager.TrackViewState()
        {
            base.TrackViewState();
            if (Parameters.Count > 0)
            {
                ((IStateManager)Parameters).TrackViewState();
            }
        }

        #endregion

        protected override void SetDirty()
        {
            base.SetDirty();
            Parameters.SetDirty();
        }

        protected override object Evaluate(System.Web.HttpContext context, System.Web.UI.Control control)
        {
            base.Evaluate(context, control);
            this.Parameters.UpdateValues(context, control);
            return null;
        }

        public void GetValues(System.Web.HttpContext context, System.Web.UI.Control control)
        {
            _values = CleanParameterValues(this.Parameters.GetValues(context, control));
            foreach (Parameter p in Parameters)
            {
                var compositeParameter = p as CompositeExpressionParameterBase;
                if (compositeParameter != null)
                    compositeParameter.GetValues(context, control);
            }
        }

        /// <summary>
        /// CleanParameterValues trims a numeric digit form the end of the parameter name which is 
        /// created by the ASP.NET controls if two or more parameters reference the same property.
        /// For example if two parameters reference the Name property, Name & Name2 will be used to 
        /// prevent collisions in the Dictionary.
        /// </summary>
        /// <param name="dictionary"></param>
        /// <returns></returns>
        private List<Pair> CleanParameterValues(IOrderedDictionary dictionary)
        {
            List<Pair> values = new List<Pair>();
            foreach (var key in dictionary.Keys)
            {
                if(dictionary[key] != null)
                    values.Add(new Pair(key, dictionary[key]));
            }

            foreach(var pair in values)
            {
                string key = (string)pair.First;
                if (char.IsDigit(key[key.Length - 1]))
                {
                    key = key.Substring(0, key.Length - 1);
                    if (dictionary.Contains(key))
                        pair.First = key;
                }
            }

            return values;
        }

        /// <summary>
        /// Returns a LambdaExpression which combines the Parameters contained in the Parameters property.  
        /// This method depends on the BuildBody to construct the the logic performed in the LambdaExpression's body.
        /// </summary>
        /// <param name="itType"></param>
        /// <returns></returns>
        public override LambdaExpression GetLambdaExpression(Type itType)
        {
            List<LambdaExpression> lambdas = new List<LambdaExpression>();
            foreach (Parameter p in Parameters)
            {
                var dynamicParameter = p as IDynamicExpressionParameter;
                if (dynamicParameter != null)
                {
                    LambdaExpression lambda = dynamicParameter.GetLambdaExpression(itType);
                    if (lambda != null)
                        lambdas.Add(lambda);
                }
            }

            if (_values != null)
            {
                foreach (var pair in _values)
                {
                    LambdaExpression lambda = DynamicExpression.ParseLambda(itType, typeof(bool), string.Format("{0} == @0", pair.First), pair.Second);
                    if (lambda != null)
                        lambdas.Add(lambda);
                }
            }

            if (lambdas.Count == 0)
                return null;

            if (lambdas.Count == 1)
                return lambdas[0];

            Expression body = BuildBody(lambdas);
            
            ParameterReferenceVisitor visitor = new ParameterReferenceVisitor();
            body = visitor.ReplaceParameters(body);

            return Expression.Lambda(body, visitor.Parameters.ToArray());
        }
        
        /// <summary>
        /// This must be implemented to construct the LambdaExpression's Body Expression.  A simple example of this is to 
        /// construct a binary tree for evaluting AND logic.
        /// </summary>
        /// <param name="lambdas">The collection of LambdaExpressions constrcuted form the Parameters property.</param>
        /// <returns>A single expression which combines the lambdas.</returns>
        protected abstract Expression BuildBody(List<LambdaExpression> lambdas);
    }
}
